package logger

import (
	"context"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"time"

	"github.com/sirupsen/logrus"
)

// Fields type, used to pass to `WithFields`.
type Fields logrus.Fields

// Logger represents the logger interface
type Logger interface {
	Debug(args ...interface{})
	Info(args ...interface{})
	Warn(args ...interface{})
	Error(args ...interface{})
	Fatal(args ...interface{})

	Debugf(format string, args ...interface{})
	Infof(format string, args ...interface{})
	Warnf(format string, args ...interface{})
	Errorf(format string, args ...interface{})
	Fatalf(format string, args ...interface{})

	WithFields(fields Fields) Logger
	WithContext(ctx context.Context) Logger
	WithError(err error) Logger
}

// Config represents logger configuration
type Config struct {
	Level        string
	Format       string
	Output       string
	ReportCaller bool
}

type logger struct {
	*logrus.Entry
}

var defaultLogger *logger

func init() {
	defaultLogger = newLogger(nil)
}

// newLogger creates a new logger instance
func newLogger(cfg *Config) *logger {
	l := logrus.New()

	// Set default configuration
	if cfg == nil {
		cfg = &Config{
			Level:        "info",
			Format:       "text",
			Output:       "stdout",
			ReportCaller: true,
		}
	}

	// Set log level
	if level, err := logrus.ParseLevel(cfg.Level); err == nil {
		l.SetLevel(level)
	}

	// Set log format
	switch cfg.Format {
	case "json":
		l.SetFormatter(&logrus.JSONFormatter{
			TimestampFormat: time.RFC3339Nano,
		})
	default:
		l.SetFormatter(&logrus.TextFormatter{
			TimestampFormat: time.RFC3339Nano,
			FullTimestamp:   true,
		})
	}

	// Set output
	switch cfg.Output {
	case "stdout":
		l.SetOutput(os.Stdout)
	case "stderr":
		l.SetOutput(os.Stderr)
	default:
		if err := os.MkdirAll(filepath.Dir(cfg.Output), 0755); err == nil {
			if file, err := os.OpenFile(cfg.Output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666); err == nil {
				l.SetOutput(io.MultiWriter(os.Stdout, file))
			}
		}
	}

	// Set caller reporting
	l.SetReportCaller(cfg.ReportCaller)

	return &logger{logrus.NewEntry(l)}
}

// Configure configures the default logger
func Configure(cfg *Config) {
	defaultLogger = newLogger(cfg)
}

// GetLogger returns the default logger
func GetLogger() Logger {
	return defaultLogger
}

// WithFields adds fields to the logging context
func WithFields(fields Fields) Logger {
	return defaultLogger.WithFields(fields)
}

// WithContext adds context to the logging context
func WithContext(ctx context.Context) Logger {
	return defaultLogger.WithContext(ctx)
}

// WithError adds an error to the logging context
func WithError(err error) Logger {
	return defaultLogger.WithError(err)
}

// Debug logs a debug message
func Debug(args ...interface{}) {
	defaultLogger.Debug(args...)
}

// Info logs an info message
func Info(args ...interface{}) {
	defaultLogger.Info(args...)
}

// Warn logs a warning message
func Warn(args ...interface{}) {
	defaultLogger.Warn(args...)
}

// Error logs an error message
func Error(args ...interface{}) {
	defaultLogger.Error(args...)
}

// Fatal logs a fatal message
func Fatal(args ...interface{}) {
	defaultLogger.Fatal(args...)
}

// Debugf logs a debug message with format
func Debugf(format string, args ...interface{}) {
	defaultLogger.Debugf(format, args...)
}

// Infof logs an info message with format
func Infof(format string, args ...interface{}) {
	defaultLogger.Infof(format, args...)
}

// Warnf logs a warning message with format
func Warnf(format string, args ...interface{}) {
	defaultLogger.Warnf(format, args...)
}

// Errorf logs an error message with format
func Errorf(format string, args ...interface{}) {
	defaultLogger.Errorf(format, args...)
}

// Fatalf logs a fatal message with format
func Fatalf(format string, args ...interface{}) {
	defaultLogger.Fatalf(format, args...)
}

// WithFields adds fields to the logging context
func (l *logger) WithFields(fields Fields) Logger {
	return &logger{l.Entry.WithFields(logrus.Fields(fields))}
}

// WithContext adds context to the logging context
func (l *logger) WithContext(ctx context.Context) Logger {
	return &logger{l.Entry.WithContext(ctx)}
}

// WithError adds an error to the logging context
func (l *logger) WithError(err error) Logger {
	return &logger{l.Entry.WithError(err)}
}

// getCallerInfo returns the file and line number of the caller
func getCallerInfo() (string, int) {
	_, file, line, ok := runtime.Caller(2)
	if !ok {
		return "unknown", 0
	}
	return filepath.Base(file), line
}
